use super::*;
use crate::Result;
use async_trait::async_trait;
use std::fs;
use std::path::PathBuf;
use tokio::sync::RwLock;

/// Configuration storage trait
#[async_trait]
pub trait ConfigStorage: Send + Sync {
    /// Load configuration from storage
    async fn load(&self) -> Result<AppConfig>;
    
    /// Save configuration to storage
    async fn save(&self, config: &AppConfig) -> Result<()>;
    
    /// Check if configuration exists
    async fn exists(&self) -> Result<bool>;
    
    /// Get configuration file path
    fn get_path(&self) -> PathBuf;
    
    /// Create backup of current configuration
    async fn create_backup(&self) -> Result<PathBuf>;
    
    /// List available backups
    async fn list_backups(&self) -> Result<Vec<PathBuf>>;
    
    /// Restore from backup
    async fn restore_backup(&self, backup_path: &PathBuf) -> Result<()>;
    
    /// Delete old backups (keep only the specified number)
    async fn cleanup_backups(&self, keep_count: usize) -> Result<()>;
}

/// File-based configuration storage implementation
pub struct FileConfigStorage {
    config_path: PathBuf,
    backup_dir: PathBuf,
}

impl FileConfigStorage {
    /// Create a new file-based configuration storage
    pub fn new(config_path: PathBuf) -> Result<Self> {
        let backup_dir = config_path.parent()
            .ok_or_else(|| ConfigError::FileNotFound("Parent directory".to_string()))?
            .join("backups");
        
        // Ensure directories exist
        if let Some(parent) = config_path.parent() {
            utils::ensure_dir_exists(&parent.to_path_buf())?;
        }
        utils::ensure_dir_exists(&backup_dir)?;
        
        Ok(Self {
            config_path,
            backup_dir,
        })
    }
    
    /// Create with default paths
    pub fn with_default_paths() -> Result<Self> {
        let config_dir = ConfigPaths::config_dir()?;
        let config_path = config_dir.join(format!("config.{}", utils::config_file_extension()));
        Self::new(config_path)
    }
    
    /// Validate configuration file format
    fn validate_config_file(&self, path: &PathBuf) -> Result<()> {
        if !path.exists() {
            return Err(ConfigError::FileNotFound(path.display().to_string()).into());
        }
        
        if !utils::is_valid_json_file(path) {
            return Err(ConfigError::InvalidFormat("Invalid JSON format".to_string()).into());
        }
        
        Ok(())
    }
    
    /// Load configuration from file with error handling
    fn load_config_from_file(&self, path: &PathBuf) -> Result<AppConfig> {
        self.validate_config_file(path)?;
        
        let content = fs::read_to_string(path)
            .map_err(|e| ConfigError::Io(e))?;
        
        let config: AppConfig = serde_json::from_str(&content)
            .map_err(|e| ConfigError::Serialization(e))?;
        
        Ok(config)
    }
    
    /// Save configuration to file with atomic write
    fn save_config_to_file(&self, path: &PathBuf, config: &AppConfig) -> Result<()> {
        let content = serde_json::to_string_pretty(config)
            .map_err(|e| ConfigError::Serialization(e))?;
        
        // Atomic write: write to temporary file first, then rename
        let temp_path = path.with_extension("tmp");
        fs::write(&temp_path, content)
            .map_err(|e| ConfigError::Io(e))?;
        
        fs::rename(&temp_path, path)
            .map_err(|e| ConfigError::Io(e))?;
        
        Ok(())
    }
}

#[async_trait]
impl ConfigStorage for FileConfigStorage {
    async fn load(&self) -> Result<AppConfig> {
        if !self.config_path.exists() {
            // Return default configuration if file doesn't exist
            return Ok(AppConfig::default());
        }
        
        self.load_config_from_file(&self.config_path)
    }
    
    async fn save(&self, config: &AppConfig) -> Result<()> {
        self.save_config_to_file(&self.config_path, config)
    }
    
    async fn exists(&self) -> Result<bool> {
        Ok(self.config_path.exists())
    }
    
    fn get_path(&self) -> PathBuf {
        self.config_path.clone()
    }
    
    async fn create_backup(&self) -> Result<PathBuf> {
        if !self.config_path.exists() {
            return Err(ConfigError::FileNotFound(
                self.config_path.display().to_string()
            ).into());
        }
        
        let backup_filename = utils::generate_backup_filename(
            self.config_path.file_name()
                .and_then(|n| n.to_str())
                .unwrap_or("config")
        );
        
        let backup_path = self.backup_dir.join(backup_filename);
        
        fs::copy(&self.config_path, &backup_path)
            .map_err(|e| ConfigError::BackupFailed(e.to_string()))?;
        
        Ok(backup_path)
    }
    
    async fn list_backups(&self) -> Result<Vec<PathBuf>> {
        if !self.backup_dir.exists() {
            return Ok(Vec::new());
        }
        
        let mut backups = Vec::new();
        let entries = fs::read_dir(&self.backup_dir)
            .map_err(|e| ConfigError::Io(e))?;
        
        for entry in entries {
            let entry = entry.map_err(|e| ConfigError::Io(e))?;
            let path = entry.path();
            
            if path.is_file() && 
               path.file_name()
                   .and_then(|n| n.to_str())
                   .map(|s| s.contains(".backup."))
                   .unwrap_or(false) {
                backups.push(path);
            }
        }
        
        // Sort by modification time (newest first)
        backups.sort_by(|a, b| {
            let a_time = fs::metadata(a).and_then(|m| m.modified()).unwrap_or(std::time::UNIX_EPOCH);
            let b_time = fs::metadata(b).and_then(|m| m.modified()).unwrap_or(std::time::UNIX_EPOCH);
            b_time.cmp(&a_time)
        });
        
        Ok(backups)
    }
    
    async fn restore_backup(&self, backup_path: &PathBuf) -> Result<()> {
        if !backup_path.exists() {
            return Err(ConfigError::FileNotFound(
                backup_path.display().to_string()
            ).into());
        }
        
        // Validate backup file before restoring
        self.validate_config_file(backup_path)?;
        
        // Create backup of current config before restoring
        if self.config_path.exists() {
            self.create_backup().await?;
        }
        
        fs::copy(backup_path, &self.config_path)
            .map_err(|e| ConfigError::BackupFailed(e.to_string()))?;
        
        Ok(())
    }
    
    async fn cleanup_backups(&self, keep_count: usize) -> Result<()> {
        let backups = self.list_backups().await?;
        
        if backups.len() <= keep_count {
            return Ok(());
        }
        
        // Remove old backups (keep only the newest ones)
        for backup_path in backups.iter().skip(keep_count) {
            if let Err(e) = fs::remove_file(backup_path) {
                eprintln!("Warning: Failed to remove backup {}: {}", 
                         backup_path.display(), e);
            }
        }
        
        Ok(())
    }
}

/// In-memory configuration storage for testing
pub struct MemoryConfigStorage {
    config: RwLock<Option<AppConfig>>,
}

impl MemoryConfigStorage {
    pub fn new() -> Self {
        Self {
            config: RwLock::new(None),
        }
    }
    
    pub fn with_config(config: AppConfig) -> Self {
        Self {
            config: RwLock::new(Some(config)),
        }
    }
}

#[async_trait]
impl ConfigStorage for MemoryConfigStorage {
    async fn load(&self) -> Result<AppConfig> {
        let config = self.config.read().await;
        Ok(config.clone().unwrap_or_default())
    }
    
    async fn save(&self, config: &AppConfig) -> Result<()> {
        let mut stored_config = self.config.write().await;
        *stored_config = Some(config.clone());
        Ok(())
    }
    
    async fn exists(&self) -> Result<bool> {
        let config = self.config.read().await;
        Ok(config.is_some())
    }
    
    fn get_path(&self) -> PathBuf {
        PathBuf::from("memory://config")
    }
    
    async fn create_backup(&self) -> Result<PathBuf> {
        // Memory storage doesn't support backups
        Err(ConfigError::BackupFailed("Memory storage doesn't support backups".to_string()).into())
    }
    
    async fn list_backups(&self) -> Result<Vec<PathBuf>> {
        Ok(Vec::new())
    }
    
    async fn restore_backup(&self, _backup_path: &PathBuf) -> Result<()> {
        Err(ConfigError::BackupFailed("Memory storage doesn't support backups".to_string()).into())
    }
    
    async fn cleanup_backups(&self, _keep_count: usize) -> Result<()> {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    
    #[tokio::test]
    async fn test_file_config_storage() {
        let temp_dir = TempDir::new().unwrap();
        let config_path = temp_dir.path().join("test_config.json");
        
        let storage = FileConfigStorage::new(config_path).unwrap();
        
        // Test loading default config when file doesn't exist
        let config = storage.load().await.unwrap();
        assert_eq!(config.version, "1.0.0");
        
        // Test saving config
        let mut config = AppConfig::default();
        config.editor.font_size = 16;
        storage.save(&config).await.unwrap();
        
        // Test loading saved config
        let loaded_config = storage.load().await.unwrap();
        assert_eq!(loaded_config.editor.font_size, 16);
        
        // Test backup creation
        let backup_path = storage.create_backup().await.unwrap();
        assert!(backup_path.exists());
        
        // Test backup listing
        let backups = storage.list_backups().await.unwrap();
        assert_eq!(backups.len(), 1);
    }
    
    #[tokio::test]
    async fn test_memory_config_storage() {
        let storage = MemoryConfigStorage::new();
        
        // Test loading default config
        let config = storage.load().await.unwrap();
        assert_eq!(config.version, "1.0.0");
        
        // Test saving and loading config
        let mut config = AppConfig::default();
        config.editor.font_size = 18;
        storage.save(&config).await.unwrap();
        
        let loaded_config = storage.load().await.unwrap();
        assert_eq!(loaded_config.editor.font_size, 18);
    }
}
